defmodule Random do
  @moduledoc false

  def random(max) do
    uniform(max)
  end

  def random(min, max) do
    strong_uniform_range(min, max)
  end

  def random_value(list) do
    random_value(list, list, 0)
  end

  defp random_value(list, [_key, w | t], sum) do
    random_value(list, t, sum + w)
  end
  defp random_value(list, [], sum) do
    random_value(list, random(1, sum))
  end

  defp random_value([_, w | t], c) when w < c do
    random_value(t, c - w)
  end
  defp random_value([key, _w | _], _c) do
    key
  end

  def random_list(list, 1) when is_list(list) do
    len = length(list)
    case len do
      0 ->
        nil
      1 ->
        hd(list)
      _ ->
        random_list(list, 1, len, [])
        |> hd
    end
  end
  def random_list(list, num) when is_list(list)  do
    len = length(list)
    if len <= num do
      list
    else
      random_list(list, num, len, [])
    end
  end
  def random_list(map, num) when is_map(map) do
     Enum.take_random(map, num)
  end
  def random_list(_, _) do
    nil
  end

  defp random_list(_, 0, _, result) do
    result
  end
  defp random_list([h | rest], num, len, result) do
    if :random.uniform() <= num / len do
      random_list(rest, num - 1, len - 1, [h | result])
    else
      random_list(rest, num, len - 1, result)
    end
  end

  def random_one(list, default \\ nil) do
    if list != [] do
      case random_list(list, 1) do
        nil ->
          default
        v ->
          v
      end
    else
      default
    end
  end

  defp uniform(n) when is_integer(n) and n < 1 do
    throw({:error, "#{n} Less Than 1"})
  end
  defp uniform(1) do
    1
  end
  defp uniform(n) when is_integer(n) do
    byte = bytes(n)
    bits = byte * 8
    <<i :: size(bits)>> = :crypto.strong_rand_bytes(byte)
    rem(i, n) + 1
  end

  defp strong_uniform_range(min, max) do
    uniform(1 + max - min) - 1 + min
  end

  defp bytes(i) when is_integer(i) and i > 0 do
    bytes(0, i)
  end
  defp bytes(bytes, 0) do
    bytes
  end
  defp bytes(bytes, i) when is_integer(i) do
    bytes(bytes + 4, :erlang.bsr(i, 32))
  end

end
